﻿using DrawTools.Tools;
using DrawTools.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;

namespace DrawTools
{
    [ContentProperty]
    public sealed partial class DrawingCanvas : Canvas
    {
        #region 字段/属性

        /// <summary>
        /// 正在进行的画图工具
        /// </summary>
        private List<IDrawTool> workingDrawTools = new List<IDrawTool>();

        private Cursor originalCursor;

        /// <summary>
        /// 原始指针
        /// </summary>
        public Cursor OriginalCursor
        {
            get => originalCursor;
            set
            {
                if (originalCursor == value) return;
                originalCursor = value;
                if (!handleCursor) this.Cursor = value;
            }
        }

        /// <summary>
        /// 鼠标指针是否正在被控制
        /// </summary>
        public Boolean handleCursor;

        public Boolean squareCursor;
        public UInt32 cursorLength;
        public Double cursorZoom;

        private Point? lastPoint;
        private DrawingCanvasViewer drawViewer;
        private Boolean canDragStart => this.Cursor == DrawCursors.Hand && drawViewer != null;
        private Boolean canDragMove => canDragStart && lastPoint.HasValue;
        /// <summary>
        /// 绘图对象
        /// </summary>
        public List<Visual> Visuals => visuals;
        /// <summary>
        /// 画笔
        /// </summary>
        public Pen Pen
        {
            get
            {
                if (DrawerConfig.Instance.IsDashStyle)
                {
                    return new Pen(this.Brush, this.StrokeThickness) { DashStyle = DrawGeometryBase.DashStyle };
                }
                return new Pen(this.Brush, this.StrokeThickness);
            }
        }

        /// <summary>
        /// 选择画笔
        /// </summary>
        public Pen SelectPen { get; }

        /// <summary>
        /// 选择底色画笔
        /// </summary>
        public Pen SelectBackgroundPen { get; }

        #endregion 字段/属性

        #region 依赖属性

        public static readonly DependencyProperty IsDashStyleProperty = DependencyProperty.Register("IsDashStyle", typeof(bool), typeof(DrawingCanvas), new PropertyMetadata(DrawerConfig.Instance.IsDashStyle, (a, b) =>
        {
            var c = (DrawingCanvas)a;
            c.IsDashStyle = b.NewValue?.Equals(true) == true;
            DrawerConfig.Instance.IsDashStyle = c.IsDashStyle;
        }));

        /// <summary>
        /// 线条是否为点线
        /// </summary>
        public bool IsDashStyle
        {
            get => (bool)this.GetValue(IsDashStyleProperty);
            set => this.SetValue(IsDashStyleProperty, value);
        }

        public static readonly DependencyProperty BrushProperty = DependencyProperty.Register("Brush", typeof(SolidColorBrush), typeof(DrawingCanvas), new PropertyMetadata(DrawerConfig.Instance.DefaultBrush, OnBrushPropertyChanged));

        private static void OnBrushPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var c = (DrawingCanvas)d;
            if (!c.isBusy)
            {
                c.stepIndex = 0;
            }
            DrawerConfig.Instance.DefaultBrush = c.Brush;
            ((DrawingCanvas)d).UpdateCursor();
        }

        /// <summary>
        /// 画刷颜色
        /// </summary>
        public SolidColorBrush Brush
        {
            get => (SolidColorBrush)this.GetValue(BrushProperty);
            set => this.SetValue(BrushProperty, value);
        }

        public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register("StrokeThickness", typeof(UInt32), typeof(DrawingCanvas), new PropertyMetadata(DrawerConfig.Instance.StrokeThickness, (a, b) =>
        {
            var c = (DrawingCanvas)a;
            DrawerConfig.Instance.StrokeThickness = c.StrokeThickness;
            c.UpdateCursor();
        }
            ));

        /// <summary>
        /// 画刷宽度
        /// </summary>
        public UInt32 StrokeThickness
        {
            get => (UInt32)this.GetValue(StrokeThicknessProperty);
            set => this.SetValue(StrokeThicknessProperty, value);
        }

        public static readonly DependencyProperty DrawingToolTypeProperty = DependencyProperty.Register("DrawingToolType", typeof(DrawToolType), typeof(DrawingCanvas), new PropertyMetadata(DrawToolType.Length, OnDrawingToolTypePropertyChanged));

        private static void OnDrawingToolTypePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var drawing = (DrawingCanvas)d;
            if (!drawing.IsKeyboardFocused) drawing.Focus();
            drawing.UpdateCursor();
        }

        /// <summary>
        /// 当前的画图工具
        /// </summary>
        public DrawToolType DrawingToolType
        {
            get => (DrawToolType)this.GetValue(DrawingToolTypeProperty);
            set => this.SetValue(DrawingToolTypeProperty, value);
        }

        public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(typeof(DrawingCanvas), new FrameworkPropertyMetadata(DrawerConfig.Instance.FontSize, FrameworkPropertyMetadataOptions.Inherits, (a, b) =>
        {
            var c = (DrawingCanvas)a;
            DrawerConfig.Instance.FontSize = c.FontSize;
        }));

        /// <summary>
        /// 文本大小
        /// </summary>
        public Double FontSize
        {
            get => (Double)this.GetValue(FontSizeProperty);
            set => this.SetValue(FontSizeProperty, value);
        }

        public static readonly DependencyProperty ZoomProperty = DrawingCanvasViewer.ZoomProperty.AddOwner(typeof(DrawingCanvas), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnZoomPropertyChanged)));

        private static void OnZoomPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (!Double.IsNaN((Double)e.NewValue))
                ((DrawingCanvas)d).UpdateCursor();
        }

        /// <summary>
        /// X轴缩放
        /// </summary>
        public Double Zoom { get => (Double)this.GetValue(ZoomProperty); set => this.SetValue(ZoomProperty, value); }

        #endregion 依赖属性

        public DrawingCanvas()
        {
            this.ClipToBounds = true;
            this.Focusable = true;
            this.OriginalCursor = Cursors.Arrow;
            this.squareCursor = false;

            this.SelectBackgroundPen = new Pen(Brushes.White, 1);
            this.SelectPen = new Pen(Brushes.Black, 1) { DashStyle = new DashStyle(new Double[] { 4 }, 0) };

            this.Loaded += delegate
            {
                this.StrokeThickness = DrawerConfig.Instance.StrokeThickness;
                this.FontSize = DrawerConfig.Instance.FontSize;
                this.Brush = DrawerConfig.Instance.DefaultBrush;
                this.IsDashStyle = DrawerConfig.Instance.IsDashStyle;
                this.Focus();
                this.drawViewer = this.FindParent<DrawingCanvasViewer>();
            };
        }

        #region 可视化

        private List<Visual> visuals = new List<Visual>();

        protected override Int32 VisualChildrenCount => visuals.Count + Children.Count;

        protected override Visual GetVisualChild(Int32 index)
        {
            if (index < visuals.Count)
                return visuals[index];
            else
                return Children[index - visuals.Count];
        }

        public void AddVisual(Visual visual)
        {
            visuals.Add(visual);

            base.AddVisualChild(visual);
            base.AddLogicalChild(visual);
        }

        public void Insert(Int32 index, Visual visual)
        {
            visuals.Insert(index, visual);

            base.AddVisualChild(visual);
            base.AddLogicalChild(visual);
        }

        public void MovoToHead(Visual visual)
        {
            var index = visuals.IndexOf(visual);

            if (index <= 0)
                return;

            visuals.RemoveAt(index);
            visuals.Insert(0, visual);
        }

        public void DeleteVisual(Visual visual)
        {
            visuals.Remove(visual);

            base.RemoveVisualChild(visual);
            base.RemoveLogicalChild(visual);
        }

        public DrawingVisual GetVisual(Point point)
        {
            var hitResult = VisualTreeHelper.HitTest(this, point);
            if (hitResult == null) return null;
            return hitResult.VisualHit as DrawingVisual;
        }

        /// <summary>
        /// 字段
        /// </summary>
        protected Typeface Typeface => new Typeface(new FontFamily("Microsoft YaHei UI,Tahoma"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);

        public FormattedText GetFormattedText(string txt)
        {
            return new FormattedText(
                   txt,
                   System.Globalization.CultureInfo.InvariantCulture,
                   FlowDirection.LeftToRight,
                   Typeface,
                   FontSize,
                   Pen.Brush,
                   Dpi.DpiValue);
        }

        #endregion 可视化

        #region 鼠标键盘事件

        protected override void OnMouseEnter(MouseEventArgs e)
        {
            IDrawTool tool;

            var point = e.GetPosition(this);
            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.TouchId == 0 && tool.CanTouchEnter && (e.Handled = tool.OnTouchEnter(point)))
                    return;
            }
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            lastPoint = null;

            IDrawTool tool;

            var point = e.GetPosition(this);
            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.TouchId == 0 && tool.CanTouchLeave && (e.Handled = tool.OnTouchLeave(point)))
                    return;
            }
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (!this.IsKeyboardFocused)
                this.Focus();

            if (this.canDragStart)
            {
                lastPoint = e.GetPosition(this.drawViewer);
                e.Handled = true;
                return;
            }

            IDrawTool tool;

            var point = e.GetPosition(this);
            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.CanTouchDown && (e.Handled = tool.OnTouchDown(0, point)))
                    return;
            }

            tool = CreateDrawingTool();

            if (tool.CanTouchDown && (e.Handled = tool.OnTouchDown(0, point)))
                return;

            if (this.canDragStart)
            {
                lastPoint = e.GetPosition(this);
                e.Handled = true;
                return;
            }
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (this.canDragMove)
            {
                var drawPoint = e.GetPosition(this.drawViewer);
                var dx = lastPoint.Value.X - drawPoint.X;
                var dy = lastPoint.Value.Y - drawPoint.Y;
                this.drawViewer.ScrollBy(dx, dy);
                lastPoint = drawPoint;
                e.Handled = true;
                return;
            }

            var point = e.GetPosition(this);

            if (point.X < 0 || point.Y < 0 || point.X > this.ActualWidth || point.Y > this.ActualHeight)
                return;

            IDrawTool tool;

            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.TouchId == 0 && tool.CanTouchMove && (e.Handled = tool.OnTouchMove(e.GetPosition(this))))
                    return;
            }
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            lastPoint = null;

            IDrawTool tool;

            var point = e.GetPosition(this);
            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.TouchId == 0 && tool.CanTouchUp && (e.Handled = tool.OnTouchUp(point)))
                    return;
            }
        }

        protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
        {
            lastPoint = null;

            IDrawTool tool;

            var point = e.GetPosition(this);
            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];
                tool.OnTouchLeave(point, true);
                e.Handled = true;
                return;
            }
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.Key == Key.Delete)
            {
                if (visuals.Count > 0 && visuals.Count > 0)
                {
                    var last = visuals.LastOrDefault();
                    if (this.DrawingToolType == DrawToolType.Pointer && this.workingDrawTools.LastOrDefault() is PointerDrawTool pd)
                    {
                        pd.OnKeyDown(Key.Delete);
                    }
                    else if (last is DrawGeometryBase dg)
                    {
                        var lastPoint = dg.DeleteLastPoint();
                        if (lastPoint != null)
                        {
                            var point = PointToScreen(lastPoint.Value);
                            Utils.NativeMethods.SetCursorPos((int)point.X, (int)point.Y);
                        }
                    }
                    return;
                }
            }
            IDrawTool tool;

            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.CanKeyDown && (e.Handled = tool.OnKeyDown(e.Key)))
                    return;
            }

            if (e.Key == Key.Space)
            {
                this.OriginalCursor = DrawCursors.Hand;
                this.squareCursor = false;
            }
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            IDrawTool tool;

            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.CanKeyUp && (e.Handled = tool.OnKeyUp(e.Key)))
                    return;
            }

            if (e.Key == Key.Space)
                this.UpdateCursor();
        }

        #endregion 鼠标键盘事件

        #region 触摸事件

        protected override void OnTouchEnter(TouchEventArgs e)
        {
            IDrawTool tool;

            var point = e.GetTouchPoint(this).Position;
            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.TouchId == e.TouchDevice.Id && tool.CanTouchEnter && (e.Handled = tool.OnTouchEnter(point)))
                    return;
            }
        }

        protected override void OnTouchLeave(TouchEventArgs e)
        {
            lastPoint = null;

            IDrawTool tool;

            var point = e.GetTouchPoint(this).Position;
            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.TouchId == e.TouchDevice.Id && tool.CanTouchLeave && (e.Handled = tool.OnTouchLeave(point)))
                    return;
            }
        }

        protected override void OnTouchDown(TouchEventArgs e)
        {
            if (!this.IsKeyboardFocused) this.Focus();

            if (this.canDragStart)
            {
                lastPoint = e.GetTouchPoint(this.drawViewer).Position;
                e.Handled = true;
                return;
            }

            IDrawTool tool;

            var point = e.GetTouchPoint(this).Position;
            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.CanTouchDown && (e.Handled = tool.OnTouchDown(0, point)))
                    return;
            }

            tool = CreateDrawingTool();

            if (tool.CanTouchDown && (e.Handled = tool.OnTouchDown(0, point))) return;

            if (this.canDragStart)
            {
                lastPoint = point;
                e.Handled = true;
                return;
            }
        }

        protected override void OnTouchMove(TouchEventArgs e)
        {
            if (this.canDragMove)
            {
                var drawPoint = e.GetTouchPoint(this.drawViewer).Position;
                var dx = lastPoint.Value.X - drawPoint.X;
                var dy = lastPoint.Value.Y - drawPoint.Y;
                this.drawViewer.ScrollBy(dx, dy);
                lastPoint = drawPoint;
                e.Handled = true;
                return;
            }

            var point = e.GetTouchPoint(this).Position;

            if (point.X < 0 || point.Y < 0 || point.X > this.ActualWidth || point.Y > this.ActualHeight)
                return;

            IDrawTool tool;

            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.TouchId == 0 && tool.CanTouchMove && (e.Handled = tool.OnTouchMove(point)))
                    return;
            }
        }

        protected override void OnTouchUp(TouchEventArgs e)
        {
            lastPoint = null;

            IDrawTool tool;

            var point = e.GetTouchPoint(this).Position;
            for (var i = workingDrawTools.Count - 1; i >= 0; i--)
            {
                tool = workingDrawTools[i];

                if (tool.TouchId == 0 && tool.CanTouchUp && (e.Handled = tool.OnTouchUp(point)))
                    return;
            }
        }

        #endregion 触摸事件

        ~DrawingCanvas()
        {
            DrawerConfig.Instance.Save();
        }
    }
}